//-------------------------------------------------------------------------------------------------
// <copyright file="TextBoxAPIHelper.cs" company="Anthony Adam">
// Copyright (c) Anthony Adam 2012. All rights reserved.
// </copyright>
//-------------------------------------------------------------------------------------------------
// ReSharper disable once CheckNamespace
namespace SuperStarRounds
{
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  /// <summary>
  /// Utility class for accessing text box positional information for words placements.
  /// </summary>
  public class TextBoxApiHelper
  {
    /// <summary>
    /// Constant definition of pixels to an inch for calculations
    /// </summary>
    private const double AnInch = 14.4;

    /// <summary>
    /// Gets the base line offset to the word being processed
    /// </summary>
    /// <param name="tb">Base TextBox class of the text box being used</param>
    /// <param name="index">Index into the text</param>
    /// <returns>Base Line Offset</returns>
    public static int GetBaselineOffsetAtCharIndex(TextBoxBase tb, int index)
    {
      var rtb = tb as RichTextBox;
      if (rtb == null)
      {
        return tb.Font.Height;
      }

      var lineNumber = rtb.GetLineFromCharIndex(index);
      var lineIndex = NativeMethods.SendMessageInt(rtb.Handle, NativeMethods.EmLineindex, new IntPtr(lineNumber), IntPtr.Zero).ToInt32();
      var lineLength = NativeMethods.SendMessageInt(rtb.Handle, NativeMethods.EmLinelength, new IntPtr(lineNumber), IntPtr.Zero).ToInt32();

      NativeMethods.CharRange charRange;
      charRange.cpMin = lineIndex;
      charRange.cpMax = lineIndex + lineLength;

      NativeMethods.Rect rect;
      rect.Top = 0;
      rect.Bottom = (int)AnInch;
      rect.Left = 0;
      rect.Right = 10000000; 

      NativeMethods.Rect rectPage;
      rectPage.Top = 0;
      rectPage.Bottom = (int)AnInch;
      rectPage.Left = 0;
      rectPage.Right = 10000000; 

      Graphics canvas = Graphics.FromHwnd(rtb.Handle);
      var canvasHdc = canvas.GetHdc();

      NativeMethods.FormatRange formatRange;
      formatRange.chrg = charRange;
      formatRange.hdc = canvasHdc;
      formatRange.hdcTarget = canvasHdc;
      formatRange.rc = rect;
      formatRange.rcPage = rectPage;

      NativeMethods.SendMessage(rtb.Handle, NativeMethods.EmFormatrange, IntPtr.Zero, ref formatRange);

      canvas.ReleaseHdc(canvasHdc);
      canvas.Dispose();

      return (int)((formatRange.rc.Bottom - formatRange.rc.Top) / AnInch);
    }

    /// <summary>
    /// Returns the index of the character under the specified
    /// point in the control, or the nearest character if there
    /// is no character under the point.
    /// </summary>
    /// <param name="txt">The text box control to check.</param>
    /// <param name="pt">The point to find the character for,
    /// specified relative to the client area of the text box.</param>
    /// <returns>Character index</returns>
    internal static int CharFromPos(TextBoxBase txt, Point pt)
    {
      unchecked
      {
        // Convert the point into a DWord with horizontal position
        // in the loword and vertical position in the hiword:
        var xy = (pt.X & 0xFFFF) + ((pt.Y & 0xFFFF) << 16);
        
        // Get the position from the text box.
        var res =
          NativeMethods.SendMessageInt(txt.Handle, NativeMethods.EmCharfrompos, IntPtr.Zero, new IntPtr(xy)).ToInt32();
        
        // the Platform SDK appears to be incorrect on this matter.
        // the hiword is the line number and the loword is the index
        // of the character on this line
        var lineNumber = (res & 0xFFFF) >> 16;
        var charIndex = res & 0xFFFF;

        // Find the index of the first character on the line within
        // the control:
        var lineStartIndex = NativeMethods.SendMessageInt(txt.Handle, NativeMethods.EmLineindex, new IntPtr(lineNumber), IntPtr.Zero).ToInt32();
        
        // Return the combined index:
        return lineStartIndex + charIndex;
      }
    }

    /// <summary>
    /// Returns the position of the specified character
    /// </summary>
    /// <param name="txt">The text box to find the character in.</param>
    /// <param name="charIndex">The index of the character whose
    /// position needs to be found.</param>
    /// <returns>The position of the character relative to the client
    /// area of the control.</returns>
    internal static Point PosFromChar(TextBoxBase txt, int charIndex)
    {
      // ReSharper disable once RedundantOverflowCheckingContext
      unchecked
      {
        int xy =
          NativeMethods.SendMessageInt(txt.Handle, NativeMethods.EmPosfromchar, new IntPtr(charIndex), IntPtr.Zero)
          .ToInt32();
        return new Point(xy);
      }
    }

    /// <summary>
    /// Gets the first visible line in a multi line text area
    /// </summary>
    /// <param name="txt">The text area text</param>
    /// <returns>First Visible Line</returns>
    internal static int GetFirstVisibleLine(TextBoxBase txt)
    {
      return NativeMethods.SendMessageInt(txt.Handle, NativeMethods.EmGetfirstvisibleline, IntPtr.Zero, IntPtr.Zero).ToInt32();
    }

    /// <summary>
    /// Gets the line index
    /// </summary>
    /// <param name="txt">The text area text</param>
    /// <param name="line">The target line number</param>
    /// <returns>The line index</returns>
    internal static int GetLineIndex(TextBoxBase txt, int line)
    {
      return NativeMethods.SendMessageInt(txt.Handle, 0xbb, new IntPtr(line), IntPtr.Zero).ToInt32();
    }

    /// <summary>
    /// Gets the width of the word from the click point
    /// </summary>
    /// <param name="tb">The text box base</param>
    /// <param name="index">The click index</param>
    /// <param name="length">The length of the clicked region</param>
    /// <returns>The width</returns>
    internal static int GetTextWidthAtCharIndex(TextBoxBase tb, int index, int length)
    {
      RichTextBox rtb = tb as RichTextBox;

      if (rtb == null) 
      { 
        return tb.Font.Height; 
      }

      NativeMethods.CharRange charRange;
      charRange.cpMin = index;
      charRange.cpMax = index + length;

      NativeMethods.Rect rect;
      rect.Top = 0;
      rect.Bottom = (int)AnInch;
      rect.Left = 0;
      rect.Right = (int)(rtb.ClientSize.Width * AnInch);

      NativeMethods.Rect rectPage;
      rectPage.Top = 0;
      rectPage.Bottom = (int)AnInch;
      rectPage.Left = 0;
      rectPage.Right = (int)(rtb.ClientSize.Width * AnInch);

      Graphics canvas = Graphics.FromHwnd(rtb.Handle);
      IntPtr canvasHdc = canvas.GetHdc();

      NativeMethods.FormatRange formatRange;
      formatRange.chrg = charRange;
      formatRange.hdc = canvasHdc;
      formatRange.hdcTarget = canvasHdc;
      formatRange.rc = rect;
      formatRange.rcPage = rectPage;

      NativeMethods.SendMessage(rtb.Handle, NativeMethods.EmFormatrange, IntPtr.Zero, ref formatRange);

      canvas.ReleaseHdc(canvasHdc);
      canvas.Dispose();
      
      return (int)((formatRange.rc.Right - formatRange.rc.Left) / AnInch);
    }

    /// <summary>
    /// Attempts to make the caret visible in a TextBox control.
    /// This will not always succeed since the TextBox control
    /// appears to destroy its caret fairly frequently.
    /// </summary>
    /// <param name="txt">The text box to show the caret in.</param>
    internal static void ShowCaret(TextBox txt)
    {
      var ret = false;
      var iter = 0;
      while (!ret && iter < 10)
      {
        ret = NativeMethods.ShowCaretAPI(txt.Handle);
        iter++;
      }
    }
  }
}